
include std/dll.e
include std/machine.e

include std/types.e
include std/io.e
include std/sequence.e as seq
include std/regex.e
include std/get.e
include std/text.e




--****
--some misc debug and formatting routines
--<<LEVELTOC depth=2>>
--=== debug output routines

sequence basedigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*"

--**
--return a string of a number from a number or sequence of numbers.
--optionally, convert from a base.(not implemented expects base10 in radix)
--optionally, convert to a base.(2, 8, 10,16, 36, 62 out radix)  
--
--untill better tested, non well formed numbers can easily crash it.
--positive numbers only
--!!switch seems broken, wonder if sequence(num) then just do atoi?
--!! X and B in format seem reversed, something is too wacky just now
export function itoa(object n, integer oradix=10, integer iradix=10)
  sequence mode
  sequence s = ""
  object num

   --problem, if call itoa("1111" with a binary can't tell from {1,2,3}
   --may have to call w/{1,1,1,1} may provide a getter/setter for binaries?
  --there is 0x for hex but nothing for binary?
  --add abs and check negitive at the end

  if sequence(n) then
    s = "{"
   num = n
  else
   num = {n}
  end if
 --have to normalize num[x], it may not even be a number or negitive
 --need to be able to handle whatever nested sequence
  --and skip strings by including them verbatium or atoi per option
 for x = 1 to length(num) do
     --handle nested sequence, assume they are also numeric
     if sequence(num[x]) then
            s &= itoa(num[x], oradix, iradix)
            if x< length(num) then
                s &= ","
            end if
         continue
      end if
  mode="%"
   switch oradix without fallthru do
     case 62 then fallthru
     case 36 then 
       mode &="s" 
       num[x] = basedigits[iff(num[x] < oradix, num[x]+1 , length(basedigits) )] 
       break
     case 8 then 
        mode &="o"
       break
     case 16 then 
       mode &="00x"  --is upper case, %X should be upper
        break
    case 2 then 
       mode &="s" 
       num[x] =text:format("[B]", {floor(num[x])})
        break
     case else 
       mode &="d"
       break
   end switch
     s &= sprintf(mode,{num[x]})
     if sequence(n) and x< length(num) then
        s &= ","
     end if
  end for


  if sequence(n) then
    s = s & "}"
  end if
  
  return s
end function


--**
--return a number from a string of a number
--optionally, convert from a base.(not implemented) -999999 on error
--optionally, convert to a base.(2, 8, 10,16, 36) .(not implemented)
--
--this should work for strings like "{0,4}" returning the sequence {0,4}.
--
--todo, make it work on a sequence of strings as well

export function atoi(sequence num, integer oradix=10, integer iradix=10)
 
   object val = value(num)
   if val[1] = GET_SUCCESS then
      return val[2] -- %radix
   end if
   return -999999
end function

--**
--return a string of a floating point number from a number
--places defaults to 4, points defaults to 3 so 12 would be 12.000
export function ftoa(atom f,integer places=4, integer points=3)
	--~ sequence format="%" & itoa(places) & "." & itoa(points) &"f"
	return sprintf(sprintf("%%%d.%df",{places,points}),f)
end function



--**
--returns the base2 bits set in a number
public function decompose(atom i, integer base= 2)
    --think was rewritten from a python blog somewhere
    --technically more acccurate than un_or_all?
    -- don't exactly have divmod nailed, should ape it
    --other than 2 bases have to concider output digits, 15=F base 16
    sequence result = {}
    integer factor = 1
    atom r
    while i > 0 do
        --~ i, r = divmod(i, base) same as (a / b, a % b).

        r = remainder(i, base)
	i = floor(i/ base)

        if r then
            result = append(result, r* factor)
	end if
        factor *= base
    end while
    --return result --do you really want it reversed?
    return reverse(result)
end function


--**
-- 
--experimental, questionaly name, possibly yagni 
--a little more complicated ternary than iff but kindof short circuits.
  --opts[i_tfc] must evaluate to an atom, later might expand on that
  --later if opts is an atom, may assume is a pointer or something?
  --also later might default to 1/0 if length is less than 3
   --might also default to 1/0 if atom and is true/false?
export function if2(object opts
     , integer i_tfc=1, integer i_tft=2, integer i_tff=3 )
  if opts[i_tfc]  then 
    return opts[i_tft] 
  end if
 return opts[i_tff]
end function


--**
--a traditional ternary iff hack no short circuits.
--someting similar just got added to the eu4 stdlib? should be builtin.
-- <eucode>
--  printf(1,"%s",{iff( a=1, "a", "b" )})
-- </eucode>

export function iff(object opts, object  a1, object b2 )
  if opts then 
    return a1
  end if
 return b2
end function




--**
--in "1.2", 12   out "        1.2"
--should it expand tabs?
--there are probably better ways in the eu4 stdlib now.
export function rjustlen(sequence strin, integer len, sequence pads=" ")
 sequence strout=""
 integer lenstrin = length(strin)

   --~ for i=1 to len-lenstrin  do strout &= pads end for
    --but now have to check since for would not happen if <1?
  if len-lenstrin>0 then
    --~ strout = join(repeat_pattern(pads, len-lenstrin))
    strout = join(repeat(pads, len-lenstrin), "")
  else
    --maybe should trim to len?
   --does euphoria have a problem with blocks of only comments in if or else?
 end if

  return strout & strin
end function


--**
 --your basic word wrap for more than one line in a message box
  --attempt to return a sequence of lines spit at len
 --recursive, is probably not too speedy with great gobs of text.
 --attempt to split at a word boundry, coma, preserve paren matches on samr line
-- 
-- but no promisses. would need to know the language & context.
-- should preserve indentation on next lines
--split on existing line endings?
  --though it passes for me it may not work in your application,
 --in spite of this having been written a few hundred thousand times already
--decided to roll my own, not even using code I have already written myself before.
 --just got to reinvent the wheel even though I know better than to try.
 --armed with some limited testing this time. 
 --added a more complicateed start at 0 when wrapping then minus then +
 --still passed all the tests the same way. oh well
-- ?is word

export function wrap_len(sequence strin, integer len, integer word=1)
 integer wl, lenstrin 
  --wgt return {s}?
  if len<1 or len >= length(strin) then return {strin} end if
  
  if word then 
   lenstrin  = length(strin)
    wl =  0
   --check backward first then forard from 0
   while 1 do
              --probbly other punct could split on, try not to split URL's? lost cause
              --to get this right would take thousands of lines of exceptions
     if len+wl < lenstrin and find(strin[len+wl], "  :;,\"'+=-\n") then
       exit
     end if

     --test variable access faster than recomputing for the 3 times it will happen     
     if wl <= 0 then  --1  guess ++1 is out too
        if wl >  floor(len* -0.15)  then
        wl -= 1
       else
         wl = 1
         end if
      else
         if wl <  floor(len* 0.08)  then
            wl += 1
          else
            wl = 0
            exit
          end if
      end if
    end while
  else
    wl = 0
   end if

  return  {strin[1..len+wl]} &wrap_len(strin[len+1+wl..$],  len,  word) 
end function



--**
-- return the M,K in a number default using 1024, not 1000.
--Si standard KB is 1000 bytes, KiB is 1024
  --is very limited right now

public function meg_a_num(object n, integer Si=0)
   --think euphoria can ignore spaces and underlines in numbers
   -- so convert coma to underline, bit counting locale
    sequence s=""
    object so
    sequence smull
    if Si then
           smull = {{1000, "KB"}, {1000E6, "MB"}, {1000E6, "GB"}, {1000E6, "TB"}, {1000E6, "PB"}}
    else
           smull = {{1024, "KiB"}, {1024E6, "MiB"}, {1024E6, "GiB"}, {1024E6, "TiB"}, {1024E6, "PiB"}}
    end if
    integer mull=smull[1][1]

    if sequence(n) then 
      n = replace_all(n, "_", "") 
      n = replace_all(n, ",", "") 
       so = atoi(n)  --atoi can't handle _ or n
    else
      so = n
    end if

    if so > mull then
      so /= mull
      s = itoa(so) & smull[1][2]
    else
      s = itoa(so)
    end if

    --should use the input n if a string and should comafy otherwise
    return s
end function

 
--  cheap comafy(1024)-> "1,024"
function commafy(object val,object sep = ",")
   sequence  s 
    s = sequence (val)
    return join(breakup(s,3),sep)
end function

--**
-- 
-- Assuming an en_US locale
-- not locale aware, test with include std/locale.e when done.
-- doesn't round unless include a format string 
-- <eucode>
-- ?comafy(1020.5) -- returns"1,020.50"
-- </eucode>

export function comafy(object n, sequence mode="%3.0f", object sep=",")
    --could get seperator from locale?
    --maybe should allow comafy non numerics?
    --should allos already spaced or underlined seperated numbers?
    --make mode default distingushable from users supplied same default
   --maybe prefix ~%f but would be better if caould thell from euphoria somehow
   --check for dp or atom(n)=n so can remove trailing dp0000 if not required
    sequence s=""
    object so
     
    --assumne it is an atom
     s = sprintf(mode, n)
      --reverse & chunk it don't add extra coma if <3 digits don't count decimal point
      --don't get confused if locale is not using decimal point or coma
    so= seq:split(s, '.')
   if length(so) >=1 and length(so[1])>3 then
      s = reverse(join(breakup(reverse(so[1]), 3), sep) ) 
  else
      s = so[1]
  end if
   if length(so)>1 then s &=  "." & so[2] end if
    return s
end function

--**
-- decorate an assumed flat sequence with it's index.
-- default ifirst=FALSE is index last.
-- default base=1 subtracts base from each index.
--
--may not need this if for example using remove_dups
--which can preserve order and return a sorted sequence.
--
--other decorators to follow
public function deco_index(sequence targ, integer ifirst=0, integer base=1)
   sequence dex = repeat({0,0}, length(targ))
    for x=1 to length(targ) do
      if ifirst then
       dex[x][1] = x-base
       dex[x][2] = targ[x]
      else
       dex[x][1] = targ[x]
       dex[x][2] = x-base
      end if
   end for
    return dex
end function

export sequence path_convert_strings = {
 {"msys /C/what", "MSYS" } 
 ,{"BS C:\\whatever", "BS" } 
 ,{"DBL C:\\\\what\\\\", "DBL" } 
 ,{"ns/ie file:///C|/what", "FILE" }
 ,{"http://localhost:8000/", "HTTP" } 
 ,{"FS C:/whatever", "FS" } 
 ,{"info ver etc (NOP)", "INFO" } 
 }


--**
-- 
--Qt seems to return forward slashed filenames c:/whatever/fn
--assumes windows forwardslashed non unicode file names and or paths
--first converts the path to a known form then converts to selects combo form
export function path_convert(sequence dpath, sequence convert = path_convert_strings[1][2])
  sequence newpath=""
 
  --~ OutputDebugString(sprintf("convert %s ", {convert}))

  --this is not a perfect reconvert but may be good enough
  dpath = trim(dpath)
   dpath =  replace_all( dpath, "\\\\",  "/")
   dpath =  replace_all( dpath, "\\",  "/")
   dpath =  replace_all( dpath, "|",  ":")
  --have to restore any file or http to normal // double either slash remove

     --  network shares not accounted for

   if  compare(convert,"MSYS") =0  then
     newpath= "/" & join(seq:split(dpath, "/"), "/")
       newpath =  replace_all(newpath,  ":", "")

   elsif   compare(convert,"DBL") =0   then
     newpath=join(seq:split(dpath, "/"), "\\\\")
    
   elsif  compare(convert,"FILE") =0  then
     newpath=  replace_all(join(seq:split(dpath, "/"), "/"),  ":", "|")
     newpath= "file:///" & newpath
       --~ newpath =  replace(newpath,  ':', '|', 6)  hangs replace__all has no start

   elsif  compare(convert,"HTTP") =0  then
       newpath =  join(seq:split(dpath, "/"), "/")
     newpath= "http://localhost:8080/" & newpath &"/"
    
   elsif   compare(convert,"FS") =0   then
     newpath=join(seq:split(dpath, "/"), "/")
    
   elsif   compare(convert,"BS") =0   then
     newpath= join(seq:split(dpath, "/"), "\\")
    
   elsif   compare(convert,"INFO") =0   then
     -- maybe popup a dlg so you can set as well as see info on each file & save it
     newpath= dpath & " info no op"

   else
     newpath= dpath & "unresolved format in convert"
   end if 

  return newpath
end function


ifdef WINDOWS then
	constant 
 	 kernel32=open_dll("kernel32.dll")
	 ,xOutputDebugString = define_c_proc(kernel32
 	  	 ,"OutputDebugStringA",{C_POINTER})
end ifdef

--**
-- in WINDOWS use with a debug viewer to save logs and see output w/o console window distraction.
-- don't wan't to see anything? just stop listening, don't start the debug viewer.
-- although there is a function that can startup the default viewer too.
-- emulated with printf on other OS.
--
-- one windows debug viewer ##Dbgview## is available on the rapideuporia archive,
--can also be obtained from MSDN or many archive sites or builtin to some windows ide.
-- <eucode>
-- OutputDebugString( sprintf("stype=%s  ", {s} ))
-- </eucode>
-- 
export procedure OutputDebugString(sequence text)
 ifdef WINDOWS then 
   atom lptext = allocate_string(text,1)
    c_proc(xOutputDebugString,{lptext})
  --~ free(lptext)
 elsedef --
    printf(2,"Db, %s ",{text})
 end ifdef
end procedure 
OutputDebugString("Db ")

--**
-- another output option, can be very slow if have many lines to output
--better to collect them all into a sequence and output at one time.
-- uses the eu4 append or write lines functions.
-- <eucode>
-- log_file(def_log , { "testing " }, "w")
-- </eucode>

export
procedure log_file(object fn, sequence lines, sequence mode="a")
    --may duplicate error to stdout and/or ring a bell
    --option overwrite log file then write_lines first call, then reset flag?
    object rtn
    --~ if atom(fn) then
    --~ else
    --~ ?1/0
    --~ end if
    
    --its putting a \n after every char pass strings as {"string"}
    if eu:compare(mode, "a")=0 then
        rtn = append_lines(fn, lines)
    else
        rtn = write_lines(fn, lines)
    end if
end procedure

